/*
 * libmekaskin : a library for Meka Robotics' skin sensor.
 * Copyright (C) 2012  Meka Robotics LLC
 * 
 * This library is free software; you can redistribute it and/or
 * modify it under the terms of the GNU Lesser General Public
 * License as published by the Free Software Foundation; either
 * version 2.1 of the License, or (at your option) any later version.
 * 
 * This library is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
 * Lesser General Public License for more details.
 * 
 * You should have received a copy of the GNU Lesser General Public
 * License along with this library; if not, write to the Free Software
 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
 */
#include <list>
#include <vector>
#include <memory>

#include <pthread.h>

#include <stdexcept>
#include <stdint.h>
#include <sys/select.h>

#ifndef TAXELARRAY_H_
#define TAXELARRAY_H_

/**
 * This exception is thrown when IO access to the hardware 
 * causes errors. 
 */
class TaxelIOException : public std::exception
{
public:
    TaxelIOException(const char* what) : std::exception(), msg(what) {};
    virtual ~TaxelIOException() throw () {};

    virtual const char* what() const throw() {
        return msg.c_str();
    }

private:
    std::string msg;
};

/** 
 * Inherit and register an instance of this 
 * this class to get notified when a new reading
 * from the sensor becomes available. 
 */
class TaxelArrayObserver {
public:
    /** 
     * This function is called when taxel values
     * are read from the device.
     */
    virtual void ReadingReceived(const std::vector<uint16_t>& readings) = 0;
};

typedef std::shared_ptr<TaxelArrayObserver> TaxelArrayObserverPtr;

/**
 * A TaxelArray object implements the logic for
 * accessing one or many skin patches 
 */
class TaxelArray {
public:
    /**
     * @param portName Something like /dev/ttyUSB0
     */
    explicit TaxelArray(std::string portName="/dev/ttyUSB0") throw(TaxelIOException);
	virtual ~TaxelArray();

    /**
     * Register an observer for event notification.
     * std::weak_ptr are used internally in order to avoid 
     * dangling pointer issues. 
     */
    void registerTaxelObserver(TaxelArrayObserverPtr observer);

    /**
     * Start reading from the device
     */
    void start();

   /**
     * Stop reading from the device
     */
    void stop();

    /**
     * @return The number of taxels on this device.
     */
    unsigned int getNumberTaxel();

    /**
     * @return The number of skin patches attached to this device.
     */
    unsigned int getNumberPatch();

    /**
     * @return The hardware version of this product
     */
    std::string getProductVersion();

    /**
     * @return The product name
     */
    std::string getProductName();

protected:

    /** 
     * Change the options on the serial port
     * associated with this device. Current version
     * runs at 57600 bauds, no parity bit.
     */
    void setOptions() throw(TaxelIOException);

    /**
     * Notify all observer that a reading was received.
     */
    void notifyReadingReceived();

    /**
     * Process incoming bytes
     */
    void processIncomingBytes(uint8_t* data, size_t nbytes);

    /**
     * @return The number of bytes available for reading.
     */
    size_t getNumberBytesAvailable();

	std::list<std::weak_ptr<TaxelArrayObserver> > observers;

private:

    TaxelArray(const TaxelArray&);

    void operator=(const TaxelArray&);

    static const size_t INCOMING_BUFFER_SIZE = 1024;

    static const size_t SKIN_PATCH_PAYLOAD_SIZE = 26;

    static const unsigned SKIN_PATCH_NUMBER_SENSORS = 12;

    enum class ProtocolFsmStates { STARTING, RECEIVING, FINISHING } portState;

    /** 
     * Convert two consecutives uint_8 values to a single uint16_t. 
     */
    uint16_t uint8ToUint16(uint8_t v1, uint8_t v2);

    /**
     * Main loop for events
     */
    static void* processEventLoop(void* data);

    int fd;
    fd_set input;
    int nfds;

    pthread_t eventThread;

    bool stopped;

    std::vector<uint8_t> partialMessage;
    std::vector<uint16_t> message;
};

#endif /* TAXELARRAY_H_ */
